package com.example.doc_searcher.searcher;

import com.example.doc_searcher.model.DocInfo;
import com.example.doc_searcher.model.Weight;
import lombok.Data;
import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Hua YY
 * Date: 2025-02-10
 * Time: 16:00
 */
@Data
public class DocSearcher {
    //停用词表文件路径
    private static String STOP_WORD_PATH = null;
    static{
        if(Config.isOnline){
            STOP_WORD_PATH = "/root/install/doc_searcher_index/stop_word.txt";
        }else {
            STOP_WORD_PATH = "D:\\doc_searcher_index\\stop_word.txt";
        }
    }
    //哈希表保存停用词
    private HashSet<String> stopWords = new HashSet<>();

    //索引对象的实例
    private Index index = new Index();
    public DocSearcher()  {
        try {
            index.load();//通过构造加载索引到内存中
            loadStopWords();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public List<Result> search(String query){
        //1：对query分词
        List<Term> oldTerms = ToAnalysis.parse(query).getTerms();//未过滤的分词结果集合
        List<Term> terms = new ArrayList<>();//过滤后的分词结果集合
        //针对分词结果，使用暂停词表进行过滤
        for(Term term : oldTerms){
            if(stopWords.contains(term.getName())){
                continue;
            }
            terms.add(term);
        }
        //2：对分词查倒排
        List<List<Weight>> termResult = new ArrayList<>();
//        List<Weight> allTermResult = new ArrayList<>();
        for (Term term : terms){
            String word = term.getName();
            List<Weight> invertedList = index.getInverted(word);//如果查不到就返回一个null
            if(invertedList == null){
                continue;
            }
//            allTermResult.addAll(invertedList);//把集合中所有Weight对象都扔到allTermResult中
            termResult.add(invertedList);
        }
        //3:[合并]对多个分词结果处发出的相同文档，进行权重合并
        List<Weight> allTermResult = mergeResult(termResult);

        //4: 按权重降序排序
        allTermResult.sort(new Comparator<Weight>() {
            @Override
            public int compare(Weight o1, Weight o2) {
                return o2.getWeight() - o1.getWeight();//降序排列
            }
        });
        //5:查正排,构造出想要的Result，返回结果
        List<Result> results = new ArrayList<>();
        for(Weight weight : allTermResult){//对每一个Weight都构建result,可能最后的结果会很多，但是用户一般只看第一页查询出来的信息，一般懒得翻页
            DocInfo docInfo = index.getDocInfo(weight.getDocId());//获取当前Weight对应的文档信息
            Result result = new Result();
            result.setTitle(docInfo.getTitle());
            result.setUrl(docInfo.getUrl());
//            result.setDesc(docInfo.getContent());//很明显把正文全部返回不合理
            result.setDesc(GenDesc(docInfo.getContent(),terms));//搞个正文简述，这个词前60个字符为起始，往后截取160个
            results.add(result);
        }
        return results;
    }
    static class Pos{
        public int row;
        public int col;
        public Pos(int row , int col){
            this.row = row;
            this.col = col;
        }
    }

    private List<Weight> mergeResult(List<List<Weight>> source) {
        //把多路合并成一路
        //1:先给每一路按升序排个序
        for (List<Weight> curRow : source){
            curRow.sort(new Comparator<Weight>() {
                @Override
                public int compare(Weight o1, Weight o2) {
                    return o1.getDocId()- o2.getDocId();
                }
            });
        }
        //2:借优先级队列合并多路
        List<Weight> target = new ArrayList<>();
        PriorityQueue<Pos> queue = new PriorityQueue<>(new Comparator<Pos>() {
            @Override
            public int compare(Pos o1, Pos o2) {
                Weight w1 = source.get(o1.row).get(o1.col);//用下标找到Weight对象
                Weight w2 = source.get(o2.row).get(o2.col);
                return w1.getDocId() - w2.getDocId();
            }
        });
        //2.1:初始化队列——把每一行第一个元素放到队列当中
        for(int row = 0 ; row < source.size() ; row++){
            queue.offer(new Pos(row,0));
        }
        //2.2:循环取队首元素（也就是当前若干行中最小的元素）
        while(!queue.isEmpty()){
            Pos curMinPos = queue.poll();
            Weight curWeight = source.get(curMinPos.row).get(curMinPos.col);

            //2.3：检查当前的Weight对象，与上一个插入到target中的对象是否是相同的对象，这里可以用Weight对象中的docId作为比较依据
            if(target.size() > 0){
                Weight lastWeight = target.get(target.size()-1);
                if(lastWeight.getDocId() == curWeight.getDocId()){
                    //文档id若相等则合并
                    int weightSum = lastWeight.getWeight() + curWeight.getWeight();
                    lastWeight.setWeight(weightSum);
                }else{
                    //文档id不相等就直接入target
                    target.add(curWeight);
                }
            }else{
                //若当前的target为空，就直接加入
                target.add(curWeight);
            }

            //2.4:考虑移动光标，当前元素处理完了之后，要把对应的这个元素光标往后移动，取这一行的下一个元素
            Pos newpos = new Pos(curMinPos.row , curMinPos.col+1);
            if(newpos.col > source.get(newpos.row).size() - 1){
                //说明光标已经超出这一行的范围了，到达末尾了，这一行就处理完了
                continue;//直接进入下一次循环
            }
            //否则把新的坐标扔到队列当中
            queue.offer(newpos);
        }
        return target;
    }

    private String GenDesc(String content , List<Term> terms){
        //遍历分词结果，看哪个
        int firstPos = -1;
        for(Term term : terms){
            String word = term.getName();
//            firstPos = content.toLowerCase().indexOf(" " + word + " ");//避免包含关系 例：查array  结果查到ArrayList
            //使用正则表达式，indexOf不支持正则，我们曲线救国！！
            content = content.toLowerCase().replaceAll("\\b" + word + "\\b" , " " + word + " ");//匹配标点符号和空格，全部替换成空格
            firstPos = content.indexOf(" " + word + " ");//女少
            if(firstPos >= 0){
                break;//找到了位置，可能这个content中会包含多个term，我们只取第一个
            }
        }
        //用户输入的词，在正文中没有出现，虽然有点扯，但还是处理一下这种情况
        if(firstPos == -1){
            if(content.length() > 160){
                return content.substring(0,160) + "...";
            }
            return content;//我去测试了一下，还真有这种情况离谱！说明查的词在标题中出现了，但是正文没出现666，这里也要对正文的长度做一下判断
        }
        //截取一部分正文
        String desc = "";
        int descBeg = firstPos < 60 ? 0 : firstPos - 60;
        if(descBeg + 160 > content.length()){
            desc = content.substring(descBeg);
        }else {
            desc = content.substring(descBeg , descBeg + 160) + "...";
        }
        for(Term term : terms){
            String word = term.getName();
            desc = desc.replaceAll("(?i) " + word + " " , "<i> " + word + "</i> ");//正则忽略大小写全字段匹配，那头单词和尾单词呢？
        }
        return desc;
    }

    public void loadStopWords(){
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(STOP_WORD_PATH))){
            while(true){
                String line = bufferedReader.readLine();//读取结果不包含\n换行
                if(line == null){
                    break;
                }
                stopWords.add(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        DocSearcher docSearcher = new DocSearcher();
        Scanner scanner = new Scanner(System.in);
        while(true){
            System.out.println("-->");
            String query = scanner.next();
            List<Result> results = docSearcher.search(query);
            for (Result result : results){
                System.out.println("==============");
                System.out.println(result);
            }
        }
    }
}
